﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Silvermoon.Controls.Classes;
using System.Drawing;

namespace Silvermoon.Controls
{
    /// <summary>
    /// Control that layouts child controls in a grid of rows and columns.
    /// </summary>
    public class Grid : CompositeControlBase
    {
        #region fields

        private bool isLayouting = false;
        private Dictionary<Control, GridEntry> controls = new Dictionary<Control, GridEntry>();
        private GridUnitCollection cols;
        private GridUnitCollection rows;

        #endregion
        #region ctor dtor

        public Grid()
            : base()
        {
            cols = new GridUnitCollection(this);
            rows = new GridUnitCollection(this);
        }

        protected override void Dispose(bool dispose)
        {
            foreach (Control c in controls.Keys)
            {
                c.Dispose();
            }
            controls.Clear();
            base.Dispose(dispose);
        }

        #endregion
        #region props

        public GridUnitCollection Columns { get { return cols; } }
        public GridUnitCollection Rows { get { return rows; } }

        #endregion
        #region methods


        public void SetControl(Control c, int col, int row)
        {
            SetControl(c, col, row, 1, 1);
        }

        public void SetControl(Control c, int col, int row, int colSpan, int rowSpan)
        {
            GridEntry entry;
            if (controls.TryGetValue(c, out entry))
            {
                entry.Row = row;
                entry.Col = col;
                entry.RowSpan = rowSpan;
                entry.ColSpan = colSpan;
            }
            else
            {
                entry = new GridEntry(c, row, col, rowSpan, colSpan);
                controls.Add(c, entry);
                c.Parent = this;
            }
            Invalidate(InvalidationFlag.Layout);
        }

        public bool RemoveControl(Control c)
        {
            if (controls.Remove(c))
            {
                c.Parent = null;
                Invalidate(InvalidationFlag.Layout);
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Removes all controls from the grid.
        /// </summary>
        public void Clear()
        {
            if (controls.Count > 0)
            {
                foreach (Control c in controls.Keys) c.Parent = null;
                controls.Clear();
                Invalidate(InvalidationFlag.Layout);
            }
        }

        protected override void OnLayout()
        {
            base.OnLayout();
            if (CheckFlag(InvalidationFlag.Layout | InvalidationFlag.Size)) LayoutGrid();
        }

        protected override void Loaded()
        {
            isLayouting = false;
            base.Loaded();
        }

        protected virtual void LayoutGrid()
        {
            isLayouting = true;
            bool hasAutoRows = rows.Any(r => r.Type == GridUnitType.Auto);
            bool hasAutoCols = cols.Any(c => c.Type == GridUnitType.Auto);
            if (hasAutoCols || hasAutoRows)
            {
                MeasureAutoControls(hasAutoRows, hasAutoCols);
            }
            MeasureRows();
            MeasureCols();
            LayoutControls();
        }

        private void LayoutControls()
        {
            foreach (GridEntry e in controls.Values)
            {
                Control c = e.Control;
                if (c.Visible)
                {
                    Rectangle rect = GetControlRect(e);
                    Rectangle r = c.Margin.Layout(c.ClientBounds, rect.Size);
                    r.Offset(rect.X, rect.Y);
                    c.Bounds = r;
                }
            }
        }

        private Rectangle GetControlRect(GridEntry e)
        {
            int x = cols[e.Col].Offset;
            int y = rows[e.Row].Offset;
            int b = rows[e.Row + e.RowSpan - 1].End;
            int r = cols[e.Col + e.ColSpan - 1].End;
            return new Rectangle(x, y, r - x, b - y);
        }

        private void MeasureAutoControls(bool hasAutoRows, bool hasAutoCols)
        {
            foreach (GridEntry entry in controls.Values)
            {
                bool isAutoRow = hasAutoRows ? IsAutoRow(entry.Row, entry.RowSpan) : false;
                bool isAutoCol = hasAutoCols ? IsAutoCol(entry.Col, entry.ColSpan) : false;
                if (isAutoRow || isAutoCol)
                {
                    int w = GetMeasureWidth(entry.Col, entry.ColSpan);
                    int h = GetMeasureHeight(entry.Row, entry.RowSpan);
                    entry.measuredSize = entry.Control.Measure(new Size(w, h));
                }
            }
        }

        private int GetMeasureHeight(int row, int rowSpan)
        {
            int h = 0;
            for (int r = row + rowSpan - 1; r >= row; r--)
            {
                GridUnit gu = rows[r];
                if (gu.Type != GridUnitType.Fixed) return 0;
                h += gu.Fixed;
            }
            return h;
        }

        private int GetMeasureWidth(int col, int colSpan)
        {
            int w = 0;
            for (int c = col + colSpan - 1; c >= col; c--)
            {
                GridUnit gu = cols[c];
                if (gu.Type != GridUnitType.Fixed) return 0;
                w += gu.Fixed;
            }
            return w;
        }

        private bool IsAutoRow(int row, int rowSpan)
        {
            while (rowSpan > 0)
            {
                rowSpan--;
                if (rows[row].Type == GridUnitType.Auto) return true;
                row++;
            }
            return false;
        }

        private bool IsAutoCol(int col, int colSpan)
        {
            while (colSpan > 0)
            {
                colSpan--;
                if (cols[col].Type == GridUnitType.Auto) return true;
                col++;
            }
            return false;
        }

        private int MeasureRows()
        {
            int h = 0;
            float scale = 0f;
            for (int i = 0; i < rows.Count; i++)
            {
                GridUnit gu = rows[i];
                switch (gu.Type)
                {
                    case GridUnitType.Fixed:
                        gu.ActualSize = gu.Fixed;
                        h += gu.Fixed;
                        break;

                    case GridUnitType.Dynamic:
                        gu.ActualSize = 0;
                        scale += gu.Dynamic;
                        break;
                }
            }

            for (int i = 0; i < rows.Count; i++)
            {
                GridUnit gu = rows[i];
                switch (gu.Type)
                {
                    case GridUnitType.Auto:
                        int max = GetMaxAutoHeight(i);
                        gu.ActualSize = max;
                        h += max;
                        break;
                }
            }

            int dh = Math.Max(0, Height - h);
            scale = scale > 0f ? (float)dh / scale : 0f;

            int offset = 0;
            for (int i = 0; i < rows.Count; i++)
            {
                GridUnit gu = rows[i];
                gu.Offset = offset;
                switch (gu.Type)
                {
                    case GridUnitType.Dynamic:
                        gu.ActualSize = (int)(gu.Dynamic * scale);
                        break;
                }
                offset += gu.ActualSize;
            }

            return h;
        }

        private int GetMaxAutoHeight(int rowIndex)
        {
            int max = 0;
            foreach (GridEntry e in controls.Values)
            {
                if (e.Row <= rowIndex && (e.Row + e.RowSpan) >= rowIndex) max = Math.Max(max, e.measuredSize.Height - FixedRowsHeight(e));
            }
            return max;
        }

        private int FixedRowsHeight(GridEntry e)
        {
            int h = 0;
            for (int r = e.RowSpan + e.Row - 1; r >= e.Row; r--)
            {
                GridUnit gu = rows[r];
                if (gu.Type == GridUnitType.Fixed) h += gu.Fixed;
            }
            return h;
        }

        private int GetMaxAutoWidth(int colIndex)
        {
            int max = 0;
            foreach (GridEntry e in controls.Values)
            {
                if (e.Col <= colIndex && (e.Col + e.ColSpan) >= colIndex) max = Math.Max(max, e.measuredSize.Width - FixedColsWidth(e));
            }
            return max;
        }

        private int FixedColsWidth(GridEntry e)
        {
            int w = 0;
            for (int c = e.ColSpan + e.Col - 1; c >= e.Col; c--)
            {
                GridUnit gu = cols[c];
                if (gu.Type == GridUnitType.Fixed) w += gu.Fixed;
            }
            return w;
        }

        private int MeasureCols()
        {
            int w = 0;
            float scale = 0f;
            for (int i = 0; i < cols.Count; i++)
            {
                GridUnit gu = cols[i];
                switch (gu.Type)
                {
                    case GridUnitType.Fixed:
                        gu.ActualSize = gu.Fixed;
                        w += gu.Fixed;
                        break;

                    case GridUnitType.Dynamic:
                        gu.ActualSize = 0;
                        scale += gu.Dynamic;
                        break;
                }
            }

            for (int i = 0; i < cols.Count; i++)
            {
                GridUnit gu = cols[i];
                switch (gu.Type)
                {
                    case GridUnitType.Auto:
                        int max = GetMaxAutoWidth(i);
                        gu.ActualSize = max;
                        w += max;
                        break;
                }
            }

            int dh = Math.Max(0, Width - w);
            scale = scale > 0f ? (float)dh / scale : 0f;
            int offset = 0;

            for (int i = 0; i < cols.Count; i++)
            {
                GridUnit gu = cols[i];
                gu.Offset = offset;
                switch (gu.Type)
                {
                    case GridUnitType.Dynamic:
                        gu.ActualSize = (int)(gu.Dynamic * scale);
                        break;
                }
                offset += gu.ActualSize;
            }

            return w;
        }

        protected override void LayoutChildren()
        {
            foreach (Control c in controls.Keys)
            {
                if (c.Visible) c.PreLoad();
            }
        }

        protected override void RenderChildren(Silvermoon.Core.Renderer renderer)
        {
            if (controls.Count > 0)
            {
                Rectangle scissorBox = renderer.ScissorBox;
                renderer.RenderControls(controls.Keys);
                renderer.ScissorBox = scissorBox;
            }
        }

        protected override void OnChildSizeChanged(Control child, Size oldSize)
        {
            base.OnChildSizeChanged(child, oldSize);
            if (!isLayouting) Invalidate(InvalidationFlag.Layout);
        }

        protected override void OnChildVisibleChanged(Control child)
        {
            base.OnChildVisibleChanged(child);
            if (!isLayouting) Invalidate(InvalidationFlag.Layout);
        }

        protected internal override IEnumerable<Control> GetAttachedChildren()
        {
            return controls.Keys;
        }

        #endregion
    }
}
